package site.sorghum.ddl.mybatis.flex;

import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import site.sorghum.ddl.DDLDialect;
import site.sorghum.ddl.DDLDialectFactory;
import site.sorghum.ddl.base.BaseDdlWrapper;
import site.sorghum.ddl.entity.DdlColumnWrap;
import site.sorghum.ddl.entity.DdlIndexWrap;
import site.sorghum.ddl.entity.DdlTableWrap;
import site.sorghum.ddl.extra.Convert;
import site.sorghum.ddl.extra.IgnoreHashMap;
import site.sorghum.ddl.extra.JdbcUtils;
import site.sorghum.ddl.proxy.ExeSqlProxy;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

/**
 * WoodDdlWrapper
 *
 * @author Sorghum
 */
@RequiredArgsConstructor
public class FlexDdlWrapper implements BaseDdlWrapper {

    @Setter
    private DDLDialect dialect = null;

    private final DataSource dataSource;

    String schema;

    /**
     * 根据数据库连接信息获取DdlDialect
     *
     * @param dbContext 数据库连接
     * @return DdlDialect
     */
    @SneakyThrows
    @Override
    public DDLDialect dialect() {
        if (dialect == null) {
            try (Connection conn = dataSource.getConnection()) {
                dialect = DDLDialectFactory.guess(conn.getMetaData().getURL());
            }
        }
        return dialect;
    }

    /**
     * 执行sql
     *
     * @param sql sql
     * @param ags 参数
     */
    @Override
    public void exec(String sql, Object... ags) {
        if (ExeSqlProxy.exeSql(sql, ags).isForceReturn()) {
            return;
        }
        JdbcUtils.executeUpdate(dataSource, sql, ags);
    }

    /**
     * 根据数据库连接信息获取表结构信息
     *
     * @param tableName 表名
     * @return 表结构信息
     */
    @Override
    public DdlTableWrap fromDataSource(String tableName) {
        return dialect().fromDataSource(
                schema(),
                tableName,
                this::sql2List,
                tableSql -> {
                    Row flexRow = Db.selectOneBySql(tableSql);
                    IgnoreHashMap<Object> row = new IgnoreHashMap<>(flexRow, IgnoreHashMap.IgnoreRule.CASE_INSENSITIVE_AND_TRIM);
                    DdlTableWrap ddlTableWrap = new DdlTableWrap();
                    ddlTableWrap.setName(tableName);
                    ddlTableWrap.setRemarks(Convert.toString(row.get("remarks")));
                    return ddlTableWrap;
                },
                columnsSql -> {
                    List<Row> mapList = Db.selectListBySql(columnsSql);
                    return mapList.stream().map(
                            it -> {
                                DdlColumnWrap ddlColumnWrap = new DdlColumnWrap();
                                IgnoreHashMap<Object> row = new IgnoreHashMap<>(it, IgnoreHashMap.IgnoreRule.CASE_INSENSITIVE_AND_TRIM);
                                ddlColumnWrap.setTable(tableName);
                                ddlColumnWrap.setName(Convert.toString(row.get("name")));
                                ddlColumnWrap.setDbType(Convert.toString(row.get("dbType")));
                                ddlColumnWrap.setSize(Convert.toInt(row.get("size")));
                                ddlColumnWrap.setDigit(Convert.toInt(row.get("digit")));
                                ddlColumnWrap.setIsNullable(Convert.toBoolean(row.get("isNullable")));
                                ddlColumnWrap.setDefaultValue(Convert.toString(row.get("defaultValue")));
                                ddlColumnWrap.setRemarks(Convert.toString(row.get("remarks")));
                                return ddlColumnWrap;
                            }
                    ).collect(Collectors.toList());
                },
                indexSql -> {
                    List<Row> rowList = Db.selectListBySql(indexSql);
                    return rowList.stream().map(
                            it -> {
                                DdlIndexWrap indexDdlWrap = new DdlIndexWrap();
                                IgnoreHashMap<Object> row = new IgnoreHashMap<>(it, IgnoreHashMap.IgnoreRule.CASE_INSENSITIVE_AND_TRIM);
                                indexDdlWrap.setTable(tableName);
                                indexDdlWrap.setIndexName(Convert.toString(row.get("indexName")));
                                indexDdlWrap.setUnique(Convert.toBoolean(row.get("isUnique")));
                                String columnNames = Convert.toString(row.get("columnNames"));
                                if (columnNames != null) {
                                    indexDdlWrap.setColumnNames(Arrays.stream(columnNames.split(",")).map(String::trim).collect(Collectors.toList()));
                                }
                                return indexDdlWrap;
                            }
                    ).collect(Collectors.toList());
                }
        );
    }

    /**
     * 获取所有表名
     *
     * @return 所有表名
     */
    @Override
    public List<String> fetchAllTables() {
        return dialect().fetchAllTableNames(
                schema(),
                this::sql2List
        );
    }

    private List<String> sql2List(String sql) {
        List<Row> dataList = null;
        dataList = Db.selectListBySql(sql);
        if (dataList.isEmpty()) {
            return Collections.emptyList();
        }
        Map<String, Object> first = dataList.get(0);
        if (first == null) {
            return Collections.emptyList();
        }
        return dataList.stream().map(
                        Map::values
                ).map(it ->
                        it.iterator().next()).map(
                        it -> it == null ? null : it.toString())
                .filter(Objects::nonNull).collect(Collectors.toList());
    }

    @SneakyThrows
    @Override
    public String schema() {
        if (schema != null) {
            return schema;
        }
        try (Connection conn = dataSource.getConnection()) {
            schema = conn.getSchema();
            if (schema == null) {
                schema = conn.getCatalog();
            }
            return schema;
        }
    }
}
